// File: jmcomic-core/src/main/java/dev/jukomu/jmcomic/option/JmOption.java
package dev.jukomu.jmcomic.option;

import dev.jukomu.common.base.packer.PackerUtils;
import dev.jukomu.common.postman.Postman;
import dev.jukomu.common.postman.impl.Postmans;
import dev.jukomu.common.util.Asserts;
import dev.jukomu.common.util.file.FileUtils;
import dev.jukomu.common.util.json.AdvancedMap;
import dev.jukomu.jmcomic.client.JmcomicClient;
import dev.jukomu.jmcomic.client.impl.JmApiClient;
import dev.jukomu.jmcomic.client.impl.JmHtmlClient;
import dev.jukomu.jmcomic.config.JmModuleConfig;
import dev.jukomu.jmcomic.entity.JmAlbumDetail;
import dev.jukomu.jmcomic.entity.JmImageDetail;
import dev.jukomu.jmcomic.entity.JmPhotoDetail;
import dev.jukomu.jmcomic.plugin.JmOptionPlugin;
import dev.jukomu.jmcomic.util.JmcomicText;
import lombok.Data;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 对应Python的JmOption，管理所有下载选项和配置。
 * 这是一个核心类，整合了DirRule、下载配置、客户端配置和插件配置。
 */
@Data
public class JmOption {

    private final DirRule dirRule; // 路径规则配置
    private final AdvancedMap download; // 下载配置
    private final AdvancedMap client; // 客户端配置
    private final AdvancedMap plugins; // 插件配置
    private String filepath; // 配置文件路径（可选）

    // 需要主线程等待完成的插件列表
    private final List<JmOptionPlugin> needWaitPlugins;

    // 缓存已创建的JmcomicClient实例
    private final Map<String, JmcomicClient> clientCache = new ConcurrentHashMap<>();

    /**
     * 构造函数。
     *
     * @param dirRule             路径规则配置Map
     * @param download            下载配置Map
     * @param client              客户端配置Map
     * @param plugins             插件配置Map
     * @param filepath            配置文件路径
     * @param callAfterInitPlugin 是否在初始化后调用after_init插件
     */
    public JmOption(Map<String, Object> dirRule, Map<String, Object> download, Map<String, Object> client, Map<String, Object> plugins, String filepath, boolean callAfterInitPlugin) {
        // 创建DirRule实例
        this.dirRule = new DirRule((String) dirRule.get("rule"), (String) dirRule.get("base_dir"));
        // 包装配置为AdvancedMap
        this.download = new AdvancedMap(download);
        this.client = new AdvancedMap(client);
        this.plugins = new AdvancedMap(plugins);
        this.filepath = filepath;

        this.needWaitPlugins = Collections.synchronizedList(new ArrayList<>());

        if (callAfterInitPlugin) {
            callAllPlugin("after_init", true, new HashMap<>()); // 假设这是插件的调用方式
        }
    }

    /**
     * 复制当前JmOption实例。
     *
     * @return 新的JmOption实例
     */
    public JmOption copyOption() {
        Map<String, Object> dirRuleMap = new HashMap<>();
        dirRuleMap.put("rule", dirRule.getRuleDsl());
        dirRuleMap.put("base_dir", dirRule.getBaseDir());

        return new JmOption(
                dirRuleMap,
                this.download.getSrcMap(),
                this.client.getSrcMap(),
                this.plugins.getSrcMap(),
                this.filepath,
                false // 复制时不再调用after_init插件
        );
    }

    // --- decide系列方法 ---

    public int decideImageBatchCount(JmPhotoDetail photo) {
        return download.get("threading", AdvancedMap.class).get("image", Integer.class);
    }

    public int decidePhotoBatchCount(JmAlbumDetail album) {
        AdvancedMap threading = download.get("threading", AdvancedMap.class);
        Integer photo = threading.get("photo", Integer.class);
        return download.get("threading", AdvancedMap.class).get("photo", Integer.class);
    }

    public String decideImageFilename(JmImageDetail image) {
        return image.getFilenameWithoutSuffix();
    }

    public String decideImageSuffix(JmImageDetail image) {
        if (image.isGif()) {
            return image.getImgFileSuffix();
        }
        String customSuffix = download.get("image", AdvancedMap.class).get("suffix", String.class);
        return customSuffix != null ? customSuffix : image.getImgFileSuffix();
    }

    public String decideImageSaveDir(JmPhotoDetail photo, boolean ensureExists) {
        String saveDir = dirRule.decideImageSaveDir(photo.getFromAlbum(), photo);
        if (ensureExists) {
            try {
                return JmcomicText.tryMkdir(saveDir);
            } catch (IOException e) {
                throw new RuntimeException("Failed to create image save directory: " + saveDir, e);
            }
        }
        return saveDir;
    }

    public String decideImageSaveDir(JmPhotoDetail photo) {
        return decideImageSaveDir(photo, true);
    }

    public String decideImageFilepath(JmImageDetail image, boolean considerCustomSuffix) {
        String saveDir = decideImageSaveDir(image.getFromPhoto());
        String suffix = considerCustomSuffix ? decideImageSuffix(image) : image.getImgFileSuffix();
        return saveDir + File.separator + FileUtils.fixWinDirName(decideImageFilename(image)) + suffix;
    }

    public String decideImageFilepath(JmImageDetail image) {
        return decideImageFilepath(image, true);
    }

    public boolean decideDownloadCache(JmImageDetail image) {
        return download.get("cache", Boolean.class);
    }

    public boolean decideDownloadImageDecode(JmImageDetail image) {
        if (image.isGif()) {
            return false;
        }
        return download.get("image", AdvancedMap.class).get("decode", Boolean.class);
    }

    // --- 创建对象相关方法 ---

    public static Map<String, Object> defaultDict() {
        return JmModuleConfig.optionDefaultDict();
    }

    public static JmOption defaultOption() {
        return construct(new HashMap<>(), true);
    }

    public static JmOption construct(Map<String, Object> userDict, boolean coverDefault) {
        Map<String, Object> dict = coverDefault ? mergeDefaultDict(userDict, defaultDict()) : userDict;

        boolean log = (boolean) dict.getOrDefault("log", true);
        if (!log) {
            JmModuleConfig.disableJmLog();
        }

        String version = (String) dict.get("version");
        if (version != null && Float.parseFloat(version) >= Float.parseFloat(JmModuleConfig.JM_OPTION_VER)) {
            // 版本号更高，直接创建
        } else {
            // 旧版本兼容
            compatibleWithOldVersions(dict);
        }

        return new JmOption(
                (Map<String, Object>) dict.get("dir_rule"),
                (Map<String, Object>) dict.get("download"),
                (Map<String, Object>) dict.get("client"),
                (Map<String, Object>) dict.get("plugins"),
                (String) dict.get("filepath"),
                true // 默认调用after_init插件
        );
    }

    private static void compatibleWithOldVersions(Map<String, Object> dict) {
        // 1. 并发配置项
        Map<String, Object> downloadMap = (Map<String, Object>) dict.get("download");
        Map<String, Object> threadingMap = (Map<String, Object>) downloadMap.get("threading");
        if (threadingMap.containsKey("batch_count")) {
            Object batchCount = threadingMap.remove("batch_count");
            threadingMap.put("image", batchCount);
        }

        // 2. 插件配置项 plugin -> plugins
        if (dict.containsKey("plugin")) {
            dict.put("plugins", dict.remove("plugin"));
        }
    }

    public Map<String, Object> deconstruct() {
        Map<String, Object> dict = new HashMap<>();
        dict.put("version", JmModuleConfig.JM_OPTION_VER);
        dict.put("log", JmModuleConfig.FLAG_ENABLE_JM_LOG);
        dict.put("dir_rule", new HashMap<String, Object>() {{
            put("rule", dirRule.getRuleDsl());
            put("base_dir", dirRule.getBaseDir());
        }});
        dict.put("download", download.getSrcMap());
        dict.put("client", client.getSrcMap());
        dict.put("plugins", plugins.getSrcMap());
        return dict;
    }

    // --- 文件IO方法 ---

    public static JmOption fromFile(String filepath) {
        try {
            Map<String, Object> dict = (Map<String, Object>) PackerUtils.unpack(filepath);
            dict.put("filepath", filepath);
            return construct(dict, true);
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException("Failed to load JmOption from file: " + filepath, e);
        }
    }

    public void toFile(String filepath) {
        String finalFilepath = (filepath != null) ? filepath : this.filepath;
        Asserts.requireTrue(finalFilepath != null, "未指定JmOption的保存路径");
        try {
            PackerUtils.pack(deconstruct(), finalFilepath);
            this.filepath = finalFilepath;
        } catch (IOException e) {
            throw new RuntimeException("Failed to save JmOption to file: " + finalFilepath, e);
        }
    }

    public void toFile() {
        toFile(null);
    }

    // --- 客户端创建方法 ---

    public JmcomicClient buildJmClient(Map<String, Object> kwargs) {
        String impl = (String) kwargs.getOrDefault("impl", client.get("impl"));
        // 使用impl作为缓存键，确保不同类型的客户端有不同的实例
        return clientCache.computeIfAbsent(impl, k -> newJmClient(kwargs));
    }

    public JmcomicClient buildJmClient() {
        return buildJmClient(new HashMap<>());
    }

    public JmcomicClient newJmClient(Map<String, Object> kwargs) {
        Map<String, Object> finalKwargs = new HashMap<>(kwargs);

        // 合并配置，并提供可靠的默认值
        String impl = (String) finalKwargs.getOrDefault("impl", this.client.get("impl", JmModuleConfig.DEFAULT_CLIENT_IMPL));

        // 确保 impl 不为 null
        if (impl == null) {
            impl = JmModuleConfig.DEFAULT_CLIENT_IMPL;
        }

        List<String> domainList = (List<String>) finalKwargs.getOrDefault("domain_list", decideClientDomain(impl));

        // 从 client 配置中获取 retry_times，如果不存在，提供默认值 5
        Object retryTimesObj = this.client.get("retry_times");
        int defaultRetryTimes = 5;
        if (retryTimesObj instanceof Number) {
            defaultRetryTimes = ((Number) retryTimesObj).intValue();
        }
        int retryTimes = (int) finalKwargs.getOrDefault("retry_times", defaultRetryTimes);

        Object cacheConfig = finalKwargs.getOrDefault("cache", this.client.get("cache"));

        // 检查 postman 配置是否存在，如果不存在则创建一个空的
        Object postmanConfObj = this.client.get("postman");
        Map<String, Object> postmanConf;
        if (postmanConfObj instanceof AdvancedMap) {
            postmanConf = new HashMap<>(((AdvancedMap) postmanConfObj).getSrcMap());
        } else if (postmanConfObj instanceof Map) {
            postmanConf = new HashMap<>((Map<String, Object>) postmanConfObj);
        } else {
            // 如果 postman 配置不存在，则从 JmModuleConfig 的默认配置中获取
            Map<String, Object> clientDefault = (Map<String, Object>) JmModuleConfig.optionDefaultDict().get("client");
            postmanConf = new HashMap<>((Map<String, Object>) clientDefault.get("postman"));
        }

        // 确保 meta_data 存在
        Map<String, Object> metaData = (Map<String, Object>) postmanConf.computeIfAbsent("meta_data", k -> new HashMap<>());
        metaData.putAll(finalKwargs); // 允许kwargs覆盖metaData

        Postman postman = Postmans.create(postmanConf);

        Class<?> clientClass = JmModuleConfig.getClientImplClass(impl);
        JmcomicClient jmClient;
        try {
            jmClient = (JmcomicClient) clientClass.getConstructor(Postman.class, List.class, int.class)
                    .newInstance(postman, domainList, retryTimes);
        } catch (Exception e) {
            throw new RuntimeException("Failed to create JmcomicClient instance for impl: " + impl, e);
        }

        CacheRegistry.enableClientCacheOnCondition(this, jmClient, cacheConfig);
        return jmClient;
    }

    public void updateCookies(Map<String, String> cookies) {
        AdvancedMap postmanConf = client.get("postman", AdvancedMap.class);
        AdvancedMap metaData = postmanConf.get("meta_data", AdvancedMap.class);

        Map<String, Object> currentCookies = metaData.get("cookies", new HashMap<>());
        currentCookies.putAll(cookies);
        metaData.put("cookies", currentCookies);
    }

    private List<String> decideClientDomain(String clientKey) {
        Object domainConfig = client.get("domain");
        if (domainConfig instanceof List && !((List) domainConfig).isEmpty()) {
            return (List<String>) domainConfig;
        }
        if (domainConfig instanceof Map && ((Map) domainConfig).containsKey(clientKey)) {
            Object domains = ((Map) domainConfig).get(clientKey);
            if (domains instanceof String) {
                return Arrays.asList(((String) domains).split("\n"));
            }
            if (domains instanceof List) {
                return (List<String>) domains;
            }
        }

        if (JmApiClient.CLIENT_KEY.equals(clientKey)) {
            return JmModuleConfig.DOMAIN_API_LIST;
        }
        if (JmHtmlClient.CLIENT_KEY.equals(clientKey)) {
            List<String> htmlDomains = JmModuleConfig.DOMAIN_HTML_LIST;
            if (htmlDomains != null && !htmlDomains.isEmpty()) {
                return htmlDomains;
            }
            try {
                return Collections.singletonList(JmModuleConfig.getHtmlDomain());
            } catch (Exception e) {
                throw new RuntimeException("Failed to get default HTML domain", e);
            }
        }

        throw new IllegalArgumentException("没有配置域名，且是无法识别的client类型: " + clientKey);
    }

    private static Map<String, Object> mergeDefaultDict(Map<String, Object> userDict, Map<String, Object> defaultDict) {
        for (Map.Entry<String, Object> entry : userDict.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            if (value instanceof Map && defaultDict.get(key) instanceof Map) {
                defaultDict.put(key, mergeDefaultDict((Map<String, Object>) value, (Map<String, Object>) defaultDict.get(key)));
            } else {
                defaultDict.put(key, value);
            }
        }
        return defaultDict;
    }

    // --- 插件调用方法 ---

    /**
     * 调用指定组的所有插件。
     *
     * @param group 插件组名（例如 "after_init", "before_album"）
     * @param safe  是否安全执行（如果为true，则捕获插件异常并打印，不中断程序）
     * @param extra 传递给插件的额外参数
     */
    @SuppressWarnings("unchecked")
    public void callAllPlugin(String group, boolean safe, Map<String, Object> extra) {
        List<Map<String, Object>> pluginList = plugins.get(group, List.class);
        if (pluginList == null || pluginList.isEmpty()) {
            return;
        }

        for (Map<String, Object> pinfo : pluginList) {
            String key = (String) pinfo.get("plugin");
            Map<String, Object> kwargs = (Map<String, Object>) pinfo.get("kwargs");

            Class<?> pclass = JmModuleConfig.REGISTRY_PLUGIN.get(key);
            Asserts.requireTrue(pclass != null, "[" + group + "] 未注册的plugin: " + key);

            try {
                invokePlugin(pclass, kwargs, extra, pinfo);
            } catch (Exception e) {
                if (safe) {
                    dev.jukomu.common.util.system.SystemUtils.printStackTrace();
                } else {
                    if (e instanceof RuntimeException) {
                        throw (RuntimeException) e;
                    }
                    throw new RuntimeException("Plugin execution failed", e);
                }
            }
        }
    }

    /**
     * 调用单个插件。
     *
     * @param pclass 插件类
     * @param kwargs 插件配置参数
     * @param extra  额外参数
     * @param pinfo  插件完整配置信息
     * @throws Exception 如果插件执行失败
     */
    private void invokePlugin(Class<?> pclass, Map<String, Object> kwargs, Map<String, Object> extra, Map<String, Object> pinfo) throws Exception {
        Map<String, Object> finalKwargs = fixKwargs(kwargs);
        if (extra != null && !extra.isEmpty()) {
            finalKwargs.putAll(extra);
        }

        try {
            // 使用反射创建插件实例并调用
            // 假设JmOptionPlugin有一个静态的build方法
            java.lang.reflect.Method buildMethod = pclass.getMethod("build", JmOption.class);
            JmOptionPlugin plugin = (JmOptionPlugin) buildMethod.invoke(null, this);

            // 设置日志开关
            boolean logEnable = (boolean) pinfo.getOrDefault("log", true);
            plugin.setLogEnable(logEnable);

            JmModuleConfig.jmLog("plugin.invoke", "调用插件: [" + plugin.getPluginKey() + "]");

            // 调用插件功能
            plugin.invoke(finalKwargs);

        } catch (java.lang.reflect.InvocationTargetException e) {
            // InvocationTargetException wraps the actual exception thrown by the invoked method.
            Throwable targetException = e.getTargetException();
            if (targetException instanceof dev.jukomu.jmcomic.plugin.PluginValidationException) {
                handlePluginValidException((dev.jukomu.jmcomic.plugin.PluginValidationException) targetException, pinfo);
            } else if (targetException instanceof dev.jukomu.jmcomic.exception.JmcomicException) {
                handlePluginJmcomicException((dev.jukomu.jmcomic.exception.JmcomicException) targetException, pclass);
            } else if (targetException instanceof Exception) {
                handlePluginUnexpectedError((Exception) targetException, pclass);
            } else {
                throw new RuntimeException("Unexpected error during plugin invocation", targetException);
            }
        } catch (Exception e) {
            // Other reflection-related errors
            throw new RuntimeException("Failed to invoke plugin: " + pclass.getName(), e);
        }
    }

    /**
     * 处理插件参数校验异常。
     */
    private void handlePluginValidException(dev.jukomu.jmcomic.plugin.PluginValidationException e, Map<String, Object> pinfo) {
        String mode = (String) pinfo.getOrDefault("valid", plugins.get("valid"));
        if ("ignore".equals(mode)) {
            return;
        }
        if ("log".equals(mode)) {
            JmModuleConfig.jmLog("plugin.validation", "插件 [" + e.getPlugin().getPluginKey() + "] 参数校验异常：" + e.getMessage());
            return;
        }
        if ("raise".equals(mode)) {
            throw e;
        }
    }

    /**
     * 处理插件中发生的未捕获的通用异常。
     */
    private void handlePluginUnexpectedError(Exception e, Class<?> pclass) {
        JmModuleConfig.jmLog("plugin.error", "插件 [" + pclass.getSimpleName() + "] 运行遇到未捕获异常，异常信息: [" + e.getMessage() + "]");
        if (e instanceof RuntimeException) {
            throw (RuntimeException) e;
        }
        throw new RuntimeException(e);
    }

    /**
     * 处理插件中发生的JmcomicException。
     */
    private void handlePluginJmcomicException(dev.jukomu.jmcomic.exception.JmcomicException e, Class<?> pclass) {
        JmModuleConfig.jmLog("plugin.exception", "插件 [" + pclass.getSimpleName() + "] 调用失败，异常信息: [" + e.getMessage() + "]");
        throw e;
    }

    /**
     * 修正插件的kwargs参数，例如解析DSL。
     */
    private Map<String, Object> fixKwargs(Map<String, Object> kwargs) {
        if (kwargs == null) {
            return new HashMap<>();
        }
        Map<String, Object> newKwargs = new HashMap<>();
        for (Map.Entry<?, ?> entry : kwargs.entrySet()) {
            Object key = entry.getKey();
            Object value = entry.getValue();

            if (value instanceof String) {
                value = JmcomicText.parseDslText((String) value);
            }

            Asserts.requireTrue(key instanceof String, "插件的kwargs参数键必须为String类型，实际类型为: " + key.getClass().getName());
            newKwargs.put((String) key, value);
        }
        return newKwargs;
    }

    public List<JmOptionPlugin> getNeedWaitPlugins() {
        return this.needWaitPlugins;
    }

    public void waitAllPluginsFinish() {
        for (JmOptionPlugin plugin : needWaitPlugins) {
            plugin.waitUntilFinish();
        }
    }

    // --- 面向对象的调用风格 ---

    public void downloadAlbum(String albumId) {
        // dev.jukomu.jmcomic.api.JmcomicApi.downloadAlbum(albumId, this, null, null, true);
        // 此处会产生循环依赖，JmOption -> JmcomicApi -> JmOption
        // 建议这些方法不在此类中提供，而是保持JmcomicApi作为唯一的顶层API入口
        throw new UnsupportedOperationException("Use dev.jukomu.jmcomic.api.JmcomicApi.downloadAlbum(albumId, option) instead.");
    }

    public void downloadPhoto(String photoId) {
        throw new UnsupportedOperationException("Use dev.jukomu.jmcomic.api.JmcomicApi.downloadPhoto(photoId, option) instead.");
    }
}